<?php

namespace eValor\BiliOpenLiveSdk\Utils;

use Curl\Curl;
use Exception;
use Throwable;

/**
 * 日志工具
 * @package eValor\XrWashSdk\Utils
 */
class SdkLog
{
    /**
     * 日志文件路径(原始)
     * @var string
     */
    private string $logPath;

    /**
     * 回收天数
     * @var int
     */
    private int $logRecycleDay;

    /**
     * 触发回收几率
     * @var int
     */
    private int $logRecycleRate;

    /**
     * Constructor.
     * @param $logPath
     * @param $logRecycleDay
     * @param $logRecycleRate
     */
    public function __construct($logPath, $logRecycleDay, $logRecycleRate)
    {
        $this->logPath = $logPath; // 原始日志文件路径
        $this->logRecycleDay = $logRecycleDay; // 回收天数
        $this->logRecycleRate = $logRecycleRate; // 回收触发几率
    }

    /**
     * 保存Curl对象的日志
     * @param $url
     * @param $reqParams
     * @param Curl|null $curlResponse
     * @param Throwable|null $throwable
     * @return void
     * @throws Exception
     */
    public function logCurl($url, $reqParams, ?Curl $curlResponse = null, ?Throwable $throwable = null)
    {
        // 日志路径为空则忽略日志
        if (!$this->logPath) {
            return;
        }

        // 生成日志内容
        $logStr = "--------------------- BiliOpenLiveSdk ---------------------";
        $logStr .= "\n请求状态: " . ($throwable ? $throwable->getMessage() : 'OK');
        $logStr .= "\n请求时间: " . date('Y-m-d H:i:s');
        $logStr .= "\n请求路径: " . $url;
        $logStr .= "\n请求参数: " . json_encode($reqParams, JSON_UNESCAPED_UNICODE);
        $logStr .= "\n响应内容: " . ($curlResponse ? $curlResponse->getResponse() : '');

        // 如果发生异常则记录异常内容
        if ($throwable) {
            $logStr .= "\n异常位置: In " . $throwable->getFile() . ' Line:' . $throwable->getLine();
        }

        // 写入日志内容
        file_put_contents($this->initPath($this->logPath), $logStr . "\n\n", FILE_APPEND);
    }

    /**
     * 初始化日志文件路径
     * @param string $logPath
     * @return string
     * @throws Exception
     */
    private function initPath(string $logPath): string
    {
        // 获取日志目录
        $logPathDir = dirname($logPath);

        // 如果日志目录不存在则建立
        if (!is_dir($logPathDir)) {
            @mkdir($logPathDir, 0755, true);
        }

        // 回收并建立日志文件
        $this->doRecycle($logPath);
        $logFileName = date('Ymd_') . basename($logPath); // 20230921_XrWashSdk.log
        $logFullPath = $logPathDir . '/' . $logFileName;

        // 建立当天的日志文件
        if (!is_file($logFullPath)) {
            @touch($logFullPath);
            @chmod($logFullPath, 0644);
        }

        // 如果仍然不是文件 则建立失败 抛出异常
        if (!is_file($logFullPath)) {
            throw new Exception('Unable to create log file at the specified location.');
        }

        // 返回已准备好的日志文件路径
        return $logFullPath;
    }

    /**
     * 进行日志自动回收
     * @param string $logPath
     * @param bool $force
     * @return void
     */
    private function doRecycle(string $logPath, bool $force = false)
    {
        try {
            if (random_int(1, 100) < $this->logRecycleRate || $force) {
                $logPathDir = dirname($logPath);
                $logFileName = basename($logPath, '.log');
                $files = glob(sprintf("%s/*_%s.log", $logPathDir, $logFileName));
                foreach ($files as $file) {
                    $baseName = basename($file, '.log');
                    if (preg_match("/^(20\d{2}[01][0-9][01][0-9])_.*?/", $baseName, $matches)) {
                        // 日志文件的时间(当日末尾)
                        $fileYear = substr($matches[1], 0, 4);
                        $fileMonth = substr($matches[1], 4, 2);
                        $fileDays = substr($matches[1], 6, 2);
                        $fileRecordTime = strtotime("{$fileYear}-{$fileMonth}-{$fileDays} 23:59:59");
                        // 日志超过指定天数则自动回收
                        $recycleTime = time() - 86400 * $this->logRecycleDay;
                        if ($fileRecordTime < $recycleTime) {
                            @unlink($file);
                        }
                    }
                }
            }
        } catch (Throwable $throwable) {

        }
    }
}
